(function(window, undefined) {

// 防止重复加载
if (window.$) { return; }


/// @overload 根据选择器及上下文匹配节点
///   @param {String} 选择器
///   @param {HTMLElement} 上下文
///   @param {Number} 匹配数量限制
/// @overload 使用现有元素(集合)创建Nodes类
///   @param {HTMLElement,HTMLCollection} 元素(集合)
///   @param {Number} 匹配数量限制
var $ = window.$ = function(selector, context, length) {
	var result;
	if ( !isNaN(context) ) {
		length = context;
		context = undefined;
	}

	if (null == selector) {
		result = [ ];
	} else if ('string' === typeof selector) {
		result = (context || document).querySelectorAll(selector);
	} else if (selector instanceof Nodes) {
		return selector;
	} else if ( selector.nodeType != null || isWindow(selector) ) {
		result = [selector];
	} else {
		result = toArray(selector);
	}

	return new Nodes(result, length);
};

// 判断对象是否window
function isWindow(obj) { return obj.alert && obj.document; };

/// 节点集合类
/// @param {Array,HTMLCollection} 节点集合
/// @param {Number} 数量限制
function Nodes(result, length) {
	for (var i = result.length - 1; i >= 0; i--) {
		this[i] = result[i];
	}
	this.length = result.length;
}
$.Nodes = Nodes;


/// #region 工具方法

var toString = Object.prototype.toString;

/// 检查变量是否函数类型
/// @param {Mixed} 待测变量
/// @return {Boolean} 待测变量是否Function类型
function isFunction(value) { return toString.call(value) === '[object Function]'; }

/// 检查变量是否Array类型
/// @param {Mixed} 待测变量
/// @return {Boolean} 待测变量是否Array类型
function isArray(value) { return toString.call(value) === "[object Array]"; }

// 把有length属性且可以通过数字索引的对象转换为数组
function toArray(arr) {
	var copy;
	try {
		copy = Array.prototype.slice.call(arr);
	} catch (e) {
		copy = [ ];
		var i = col.length;
		while (i) {
			copy[--i] = arr[i];
		}
	}
	return copy;
}

/// 把源对象的属性和方法扩展到目标对象上
/// @param {Mixed} 目标对象
/// @param {Mixed} 源对象
/// @return {Mixed} 已扩展的目标对象
function extend(target) {
	for (var i = 1, p, src; i < arguments.length; i++) {
		src = arguments[i];
		if (src) {
			for (p in src) {
				target[p] = src[p];
			}
		}
	}

	return target;
}

/// 对指定对象的每个元素执行指定操作
/// @param {Mixed} 遍历的对象
/// @param {Function} 操作函数，function(elem, index, obj)，this指向当前元素
/// @return {Mixed} 遍历的对象
function each(obj, callback) {
	if (obj != null) {
		var i = -1, len = obj.length;
		if ( len === undefined || isFunction(obj) ) {
			for (i in obj) {
				if ( false === callback.call(obj[i], obj[i], i, obj) ) {
					break;
				}
			}
		} else {
			while (++i < len) {
				if ( false === callback.call(obj[i], obj[i], i, obj) ) {
					break;
				}
			}
		}
	}
	
	return obj;
}

/// Get First, Set All 访问器
/// @param {Mixed} 访问对象
/// @param {String,Mixed} 访问键名，如果为Object类型，则以对象的每个属性和值递归调用此函数
/// @param {Mixed} 访问值，如果为undefined，则为get first操作，否则为set all操作
/// @param {Boolean} 当value为函数时，是否执行函数并以函数范围值作为最终值
/// @param {Object} 访问函数，包括：
///   get(firstElement, key): get first操作，this指向访问对象
///   set(currentElement, key, value): set all操作，this指向访问对象
/// return {Mixed} get first操作返回键名对应的值；set all操作返回访问对象
function access(elems, key, value, isExec, fns) {
	var len = elems.length, i, temp;
	
	if (typeof key === 'object') {
		for (var k in key) {
			access(elems, k, key[k], isExec, fns);
		}
		return elems;
	}

	if (value !== undefined) {
		isExec = isExec && isFunction(value);

		i = -1;
		while (++i < len) {
			fns.set.call( elems, elems[i], key,
				isExec ? value.call( elems[i], i ) : value );
		}
	
		return elems;
	}

	return len ? fns.get.call(elems, elems[0], key) : undefined;
}

/// 获取对象的第一个元素
/// @param {Object,Array} 对象或数组
/// @return {Object} 第一个元素 { key: value }
function first(obj) {
    if (obj != null) {
		if ( obj.length === undefined || isFunction(obj) ) {
			for (i in obj) {
			    if ( obj.hasOwnProperty(i) ) {
				    return {
				        key : i,
				        value : obj[i]
				    };
				}
			}
		} else {
			return {
			    key : 0,
			    value : obj[0]
			};
		}
	}
}

var tplCache = { };
/// 模板转换
/// @param {String} 模板代码
/// @param {Object} 值集合
/// @param {Boolean} 是否缓存模板，默认为是
/// @return {String} 转换后的代码
function parseTpl(tpl, values, isCached) {
	if (null == tpl) { return; }
	if (null == values) { return tpl; }

	var fn = tplCache[tpl];
	if (!fn) {
		fn = new Function("obj", "var _=[];with(obj){_.push('" +
				tpl.replace(/[\r\t\n]/g, " ")
				.replace(/'(?=[^#]*#>)/g, "\t")
				.split("'").join("\\'")
				.split("\t").join("'")
				.replace(/<#=(.+?)#>/g, "',$1,'")
				.split("<#").join("');")
				.split("#>").join("_.push('")
				+ "');}return _.join('');");
		isCached !== false && (tplCache[tpl] = fn);
	}
	
	return fn(values);
}

/// 检测对象是否空对象
/// @param {Object} 对象
/// @return {Boolean} 对象是否空对象
function isEmptyObject(obj) {
    for (var i in obj) {
        return false;
    }
    return true;
}

/// 检测对象是否只有一个自定义属性
/// @param {Object} 对象
/// @return {Boolean} 对象是否只有一个自定义属性
function isOnlyOneAttr(obj) {
    var temp = false, i;
    for (i in obj) {
        if ( obj.hasOwnProperty(i) ) {
            if (false === temp) {
                temp = true;
            } else {
                return false;
            }
        }
    }
    
    return temp;
}


extend($, {
	extend : extend,
	each : each,
	access : access,
	parseTpl : parseTpl,
	isArray : isArray,
	isFunction : isFunction,
	first : first,
	isEmptyObject : isEmptyObject,
	isOnlyOneAttr : isOnlyOneAttr
});

/// #endregion


/// #region DOM操作

var attrMap = {
	'class' : 'className',
	'for' : 'htmlFor'
};	// HTML元素特殊属性名映射

$.dom = {
	getAttr : function(el, key) { return el[attrMap[key] || key]; },
	setAttr : function(el, key, val) { el[attrMap[key] || key] = val; }
};

/// #endregion


/// #region 样式操作

var FLOAT_NAME = 'cssFloat',
	STYLE_NOTIN_PXS = /^(?:zIndex|fontWeight|opacity|zoom|)$/,
	DASH = /-([a-z])/gi,
	MULTI_SPACE = /\s+/g;

function getCurrentStyle(el, name) {
	return el.ownerDocument.defaultView.getComputedStyle(el, null)[name];
}
// 修复样式名
function fixStyleName(name) {
	return 'float' === name.toLowerCase() ? FLOAT_NAME : name.replace(DASH, function($0, $1) {
		return $1.toUpperCase();
	});
}
// 修复样式值
function fixStyleValue(name, val) {
	return STYLE_NOTIN_PXS.test(name) || '' === val || isNaN(val) ? val : val + 'px';
}
// 解析类名
function explainClassName(className) {
	var temp;
	if ('string' === typeof className) {
		temp = className.replace(MULTI_SPACE, ' ');
		className = className.split(' ');
	} else {
		temp = className.join(' ');
	}
	className.raw = temp;

	return className;
}
// 获取元素大小
function getSize(elem, which, extra) {
	if (!elem) { return; }

	which = which.replace(/^[a-z]/, function($0) { return $0.toUpperCase(); });		// 首字母大写
	extra = extra || '';
	
	var size = elem['offset' + which],
		borders = 'Width' == which ? ['Left', 'Right'] : ['Top', 'Bottom'];
   
	for (var i = 0, b; b = borders[i]; i++) {
		if ( -1 == extra.indexOf('padding') ) {
			size -= parseFloat( $.style.getCss(elem, 'padding' + b) ) || 0;
		}
		if ( -1 == extra.indexOf('border') ) {
			size -= parseFloat( $.style.getCss(elem, 'border' + b + 'Width') ) || 0;
		}
		if ( extra.indexOf('margin') != -1 ) {
			size += parseFloat( $.style.getCss(elem, 'margin' + b) ) || 0;
		}
	}
	
	return size;
}

$.style = {
	getCss : function(el, key) {
		key = fixStyleName(key);
		if (el.style[key] !== undefined) {
			return el.style[key] !== '' ? el.style[key] : getCurrentStyle(el, key);
		}
	},
	setCss : function(el, key, val) {
		key = fixStyleName(key);
		val = fixStyleValue(key, val);
		var curVal = el.style[key];
		if (curVal !== undefined && curVal != val) {
			el.style[key] = val;
		}
	},
	hasClass : function(el, classes) {
	    if (el.className != '') {
	        var className = ' ' + el.className + ' ', i = classes.length;
	        while (--i >= 0) {
	            if ( className.indexOf(' ' + classes[i] + ' ') == -1 ) {
	                return false;
	            }
	        }
	        return true;
	    } else {
	        return false;
	    }
	},
	addClass : function(el, classes) {
		if ('' === el.className && classes.raw) {
			el.className = classes.raw;
		} else {
			var className = ' ' + el.className + ' ', i = -1, len = classes.length;
			while (++i < len) {
				if ( -1 === className.indexOf(' ' + classes[i] + ' ') ) {
					className += (classes[i] + ' ');
				}
			}
			el.className = className.trim();
		}
	},
	removeClass : function(el, classes) {
		if (classes) {
			switch (el.className) {
				case classes.raw:
					el.className = '';
				break;

				case '':
					return;
				break;
				
				default:
					var className = ' ' + el.className + ' ', i = -1, len = classes.length;
					while (++i < len) {
						className = className.replace(' ' + classes[i] + ' ', ' ');
					}
					el.className = className.trim();
				break;
			}
		} else {
			el.className = '';
		}
	}
};

/// #endregion


/// #region 事件操作

$.event = {
	on : function(el, eventNames, handler) {
		each(eventNames, function(name) {
			el.addEventListener(name, handler, false);
		});
	},
	detach : function(el, eventNames, handler) {
		each(eventNames, function(name) {
			el.removeEventListener(name, handler, false);
		});
	}
};

/// #endregion


/// #region 动态加载

var encodeURIComponent = window.encodeURIComponent, setTimeout = window.setTimeout;

// 转换为URL参数
function toQueryString(values) {
	if (values) {
		var str = [];
		for (var n in values) {
			values[n] != null && str.push(n + "=" + encodeURIComponent(values[n]));
		}
		return str.join("&").replace(/%20/g, "+");
	}
}

$.ajax = {

	/// 发送Ajax请求
	/// @param {Object} 参数
	///   url {String} 请求地址
	///   data {Object} 发送数据
	///   method {String} 发送方法，GET或POST
	///   async {Boolean} 是否异步请求，默认为是
	///   timeout {Number} 超时时间，仅异步请求下有效
	///   onSuccess {Function} 请求成功后进行的操作，function(xhr)
	///   onError {Function} 请求失败后进行的操作，function(xhr)
	/// @return {XMLHttpRequest} XmlHttpRequest对象
	send : function(options) {
		var xhr = new XMLHttpRequest(), hasCompleted,
			method = options.method ? options.method.toUpperCase() : "GET";
		
		options.async = null == options.async ? true : Boolean(options.async);
		
		options.data = options.data || { };
		
		// 连接参数键值对
		var postData, url = options.url;
		if (options.data) {
			postData = toQueryString(options.data);
			if ("GET" === method && postData) {
				url += (url.indexOf("?") != -1 ? "&" : "?") + postData;
				postData = undefined;
			}
		}
		
		// 添加随机数
		if (options.useRndNum !== false) {
		    url += (url.indexOf("?") != -1 ? "&" : "?") + "rnd=" + Math.random();
		}
		
		var stateChange = function() {
			if (4 == xhr.readyState) {
				hasCompleted = true;
				options.onLoad && options.onLoad.call(this, xhr);
				var eventName = 200 == xhr.status ? "onSuccess" : "onError";
				options[eventName] && options[eventName].call(this, xhr);
				options.onComplete && options.onComplete.call(this, xhr);
			}
		};

		if (options.async) {
			options.timeout > 0 && setTimeout(function() {
				if (!hasCompleted) {
					xhr.abort();
					options.onLoad && options.onLoad.call(this, xhr);
					options.onTimeout && options.onTimeout(xhr);
					options.onComplete && options.onComplete.call(this, xhr);
				}
			}, options.timeout);

			xhr.onreadystatechange = stateChange;
		}

		xhr.open(method, url, options.async, options.username, options.password);
		
		xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
		if ("POST" === method) {
			xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
		}

        if (options.headers) {
            $.each(options.headers, function(val, key) {
                xhr.setRequestHeader(key, val);
            });
        }
		
		xhr.send(postData);

		!options.async && stateChange();

		return xhr;
	},

	/// 发送Ajax请求，并把请求结果转换为JSON
	/// @param {Object} 参数
	///   url {String} 请求地址
	///   data {Object} 发送数据
	///   async {Boolean} 是否异步请求，默认为是
	///   timeout {Number} 超时时间，仅异步请求下有效
	///   onSuccess {Function} 请求成功后进行的操作，function(data)
	///   onError {Function} 请求失败后进行的操作，function(xhr)
	/// @return {XMLHttpRequest} XmlHttpRequest对象
	getJSON : function(options) {
		var _onSuccess = options.onSuccess;
		if (_onSuccess) {
			options.onSuccess = function(xhr) {
				var data;
				try {
					data = JSON.parse(xhr.responseText);
				} catch (e) {
					console.log("Error in getJSON:" + xhr.responseText);
				}

				_onSuccess.call(this, data);
			};
		}
		return this.send(options);
	}
};

/// #endregion


extend(Nodes.prototype, {

	/// 对当前所有节点进行指定操作
	/// @param {Function} 操作函数
	/// @return {Nodes} 当前节点
	each : function(callback) { return each(this, callback); },

	/// @overload 获取当前第一个节点的属性
	///   @param {String} 属性名
	///   @return {Mixed} 属性值
	/// @overload 设置当前所有节点的属性
	///   @param {String,Object} 属性名或属性键值对
	///   @param {Mixed} 属性值
	///   @return {Nodes} 当前节点集合
	attr : function(key, value) {
		return access(this, key, value, true, {
			get : $.dom.getAttr,
			set : $.dom.setAttr
		});
	},
	
	/// 获取当前第一个节点的父节点
    /// @return {HTMLElement} 当前第一个节点的父节点
	parent : function() {
	    return this[0] ? this[0].parentNode : undefined;
	},

	/// @overload 获取当前第一个节点的innerHTML
	///   @return {String} innerHTML
	/// @overload 设置当前所有节点的innerHTML
	///   @param {String} innerHTML
	///   @return {Nodes} 当前节点集合
	html : function(value) {
		return this.attr("innerHTML", value);
	},
	
	val : function(value) {
	    return this.attr("value", value);
	},
	
	/// 添加事件监听函数
	/// @param {String} 事件名，多个事件用逗号隔开
	/// @param {Function} 事件监听函数
	/// @return {Nodes} 当前节点集合
	on : function(eventNames, handler) {
		eventNames = eventNames.split(",");
		return this.each(function(el) {
			$.event.on(el, eventNames, handler);
		});
	},

	/// 移除事件监听函数
	/// @param {String} 事件名，多个事件名用逗号隔开
	/// @param {Function} 事件监听函数
	/// @return {Nodes} 当前节点集合
	detach : function(eventNames, handler) {
		eventNames = eventNames.split(",");
		return this.each(function(el) {
			$.event.detach(el, eventNames, handler);
		});
	},

	/// @overload 获取当前第一个节点的CSS样式
	///   @param {String} 样式名
	///   @return {String} 样式值
	/// @overload 设置当前所有节点的CSS样式
	///   @param {String,Object} 样式名或样式键值对
	///   @param {String} 样式值
	///   @return {Nodes} 当前节点
	css : function(key, val) {
		return access(this, key, val, true, {
			get : $.style.getCss,
			set : $.style.setCss
		});
	},

	/// 为当前所有节点添加CSS样式类
	/// @param {String,Array} 样式类名
	/// @return {Nodes} 当前节点
	addClass : function(className) {
		className = explainClassName(className);
		
		if (className.length > 0) {
			this.each(function(el) {
				$.style.addClass(el, className);
			});
		}

		return this;
	},
	
	hasClass : function(className) {
	    if (className != null && className !== "") {
			className = explainClassName(className);
		}
		var flag = true;
		
		this.each(function(el) {
			flag = flag && $.style.hasClass(el, className);
			return flag;
		});

		return flag;
	},

	/// 为当前所有节点移除CSS样式类
	/// @param {String,Array} 样式类名，如果为空，则移除所有样式
	/// @return {Nodes} 当前节点
	removeClass : function(className) {
		if (className != null && className !== "") {
			className = explainClassName(className);
		}
		this.each(function(el) {
			$.style.removeClass(el, className);
		});

		return this;
	},

	/// 获取元素占用的宽度
	/// @return {Number} 元素占用的宽度
	width : function(extra) { return getSize(this[0], 'width', extra); },
	
	/// 获取元素占用的高度
	/// @return {Number} 元素占用的高度
	height : function(extra) { return getSize(this[0], 'height', extra); },
	
	remove : function() {
	    var el = this[0];
	    if (el) {
	        var parent = el.parentNode;
	        parent && parent.removeChild(el);
	    }
	},
	
	push : Array.prototype.push,
	
	indexOf : Array.prototype.indexOf,
	
	unshift : Array.prototype.unshift,
	
	shift : Array.prototype.shift
});


// 把数字转换成两位数的字符串
function to2Digit(num) { return num < 10 ? "0" + num : num; }
// 24小时制 -> 12小时制
function to12Hours(hours) { return hours > 12 ? hours - 12 : hours; }
// 记录默认的Date.prototype.toString
var _dateToString = Date.prototype.toString;
// 重写toString
Date.prototype.toString = function(f) {
    var t = this;
    
    return f ? f.replace(/y+|m+|d+|h+|s+|H+|M+|t+/g, function($0) {
        switch ($0) {
		    case "yyyy": return t.getFullYear();
		    case "yy": return t.getFullYear().toString().slice(-2);
		    case "MM": return to2Digit( t.getMonth() + 1 );
		    case "M": return t.getMonth() + 1;
		    case "dd": return to2Digit( t.getDate() );
		    case "d": return t.getDate();
		    case "HH": return to2Digit( t.getHours() );
		    case "H": return t.getHours();
		    case "hh": return to2Digit( to12Hours(t.getHours()) );
		    case "h": return to12Hours( t.getHours() );
		    case "mm": return to2Digit( t.getMinutes() );
		    case "m": return t.getMinutes();
		    case "ss": return to2Digit( t.getSeconds() );
		    case "s": return t.getSeconds();
		    case "t": return t.getHours() > 12 ? "PM" : "AM";
		    default: return $0;
	    }
    }) : _dateToString.call(t);
};

/// 检测指定日期是否今天
/// @param {String,Date} 日期
/// @return {Boolean} 指定日期是否今天
Date.isToday = function(val) {
    var now = new Date();
    
    if ("string" === typeof val) {
        val = new Date(val);
    }
    
    return now.getFullYear() === val.getFullYear() &&
        now.getMonth() === val.getMonth() &&
        now.getDate() === val.getDate();
};

/// 去掉字符串两端的空白
/// @return {String} 修整后的字符串
String.prototype.trim = String.prototype.trim || function() {
    return this.replace(/^\s+/, "").replace(/\s+$/, "");
};


var defaultEncode = window.encodeURIComponent,
	defaultDecode = window.decodeURIComponent;

/// @overload 获取URL锚点中的字典数据
///   @param {String} 键名
///   @return {Mixed} 键值
/// @overload 设置URL锚点中的字典数据
///   @param {String} 键名
///   @param {Mixed} 键值
$.hash = function(name, val) {
    var location = window.location, hash = String(location.hash);
    try {
        hash = JSON.parse( defaultDecode( hash.replace(/^#/, "") ) );
    } catch(e) {
        
    }
    
    switch (arguments.length) {
        case 2:
            hash = hash || { };
            if (val == null) {
                delete hash[name];
            } else {
                hash[name] = val;
            }
            location.hash = defaultEncode( JSON.stringify(hash) );
        break;
    
        case 1:
            return hash ? hash[name] : undefined;
        break;
        
        case 0:
            return hash;
        break;
    }
};


/// Cookie操作
$.cookie = {

    /// 设置cookie
    /// @param {String} cookie名
    /// @param {String} cookie值
    /// @param {Object} 选项
	///   domain {String} 域
	///   path {String} 路径
	///   expires {Date,Number,String} 过期时间
	///   secure {Boolean} 是否只在安全连接中有效
	///   encode {Function} cookie编码函数，默认为encodeURIComponent
	set : function (name, value, o) {
		o = extend({
			encode : window.encodeURIComponent
		}, o);
			
		var expires = o.expires,
			text = o.encode(name) + '=' + o.encode(value);
		
		if (expires){
			text += '; expires=' + expires.toUTCString();
		}

		if ( o.path ){
			text += '; path=' + o.path;
		}
		if ( o.domain ){
			text += '; domain=' + o.domain;
		}
		if (o.secure === true){
			text += '; secure';
		}
		
		document.cookie = text;
	},
	
    /// 获取cookie
    /// @param {String} cookie名
	/// @param {Object} 选项
	///   encode {Function} cookie编码函数，默认为encodeURIComponent
	///   decode {Function} cookie解码函数，默认为decodeURIComponent
    /// @return {String} cookie值
	get : function(name, o) {
		o = extend({
			encode: defaultEncode,
			decode: defaultDecode
		}, o);

		name = '; ' + o.encode(name) + '=';
		var cookie = '; ' + document.cookie, beginPos = cookie.indexOf(name), endPos;
		
		if (beginPos === -1) {
			return;
		}

		beginPos += name.length;
		endPos = cookie.indexOf(';', beginPos);
		if (endPos === -1) {
			endPos = cookie.length;
		}

		return o.decode( cookie.substring(beginPos, endPos) );
	},
	
    /// 删除cookie
    /// @param {String} cookie名
    /// @param {Object} 选项
	///   domain {String} 域
	///   path {String} 路径
	///   secure {Boolean} 是否只在安全连接中有效
	///   encode {Function} cookie编码函数，默认为encodeURIComponent
	remove : function (name, o) {
		o = o || { };
		o.expires = new Date(0);
        
        this.set(name, '', o);
	}
};

})(window);